package com.zwh.se.design_pattern.creational.singleton;

/**
 * 懒汉式（线程安全版本——DCL(双重检查锁)）<p>
 * 优点: 既支持懒加载，同时又支持高并发的单例实现方式。
 * 缺点: 编写代码麻烦
 *
 * @author yexi
 */
public class LazySafetyV3 {
    //保持该类在系统中有且只有一个实例
    /**
     * 必须要为单例添加 volatile 关键字(详细讲解为什么):
     * 在java内存模型中，volatile 关键字作用可以是保证可见性或者禁止指令重排。这是因为 singleton = new Singleton() 这个语句
     * 它本身并非是一个原子操作。
     * 事实上，在 JVM 中上述语句至少做了以下这 3 件事:
     * ·第一步是给 singleton 分配内存空间；
     * ·第二步开始调用 Singleton 的构造函数等，来初始化 singleton；
     * ·第三步，将 singleton 对象指向分配的内存空间（执行完这步 singleton 就不是 null 了）。
     * 这里需要留意一下 1-2-3 的顺序，因为存在指令重排序的优化，也就是说第 2 步和第 3 步的顺序是不能保证的。
     * 最终的执行顺序可能是 1-2-3，也有可能是 1-3-2。
     * 如果是 1-3-2，那么在第 3 步执行完以后，singleton 就不是 null 了，可是这时第 2 步并没有执行，singleton 对象未完成初始化，
     * 它的属性的值可能不是我们所预期的值。
     * 假设此时线程 2 进入 getInstance 方法，由于 singleton 已经不是 null 了，所以会通过第一重检查并直接返回，
     * 但其实这时的 singleton 并没有完成初始化，所以使用这个实例的时候会报错。
     * <p>
     * 使用了 volatile 之后，相当于是表明了该字段的更新可能是在其他线程中发生的，因此应确保在读取另一个线程写入的值时，
     * 可以顺利执行接下来所需的操作。
     * 在 JDK 5 以及后续版本所使用的 JMM 中，在使用了 volatile 后，会一定程度禁止相关语句的重排序，
     * 从而避免了上述由于重排序所导致的读取到不完整对象的问题的发生。
     */
    private static volatile LazySafetyV3 INSTANCE;

    //私有化构造方法
    private LazySafetyV3() {
    }

    //对外界提供统一调用方法获取单例对象
    public static LazySafetyV3 getInstance() {
        //为什么要外面也要检查一次呢？因为初始化之后，下面的同步代码块部分就没用了啊！
        if (INSTANCE == null) {
            //减小了同步代码块
            synchronized (LazySafetyV3.class) {
                //双重检查：进入同步代码块之后再检查一次
                if (INSTANCE == null) {
                    try {
                        //模拟在创建对象的过程中稍微出了点问题或者说业务需要，导致慢了一点点。
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //只在调用了方法时才进行初始化(方法调用时才实例化，所以解决了饿汉式的缺点）
                    INSTANCE = new LazySafetyV3();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() ->
                    System.out.println(LazySafetyV3.getInstance().hashCode())
            ).start();
        }
    }

}
